﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Controls.Primitives;

namespace RadiantQ.Windows.Controls
{
    /// <summary>
    /// A TreeView instance that is used to &quot;resolve&quot; the
    /// hierarchy into a flat list.
    /// </summary>
    /// <remarks>
    /// <para> The <see
    /// cref="P:RadiantQ.Windows.Controls.FlatteningTreeView.FlatItemsSource">FlatItemsSource
    /// </see>is a list that gets added with <see
    /// cref="T:RadiantQ.Windows.Controls.FlatNodeData">FlatNodeData
    /// </see>instances as tree nodes are realized. Bind this list to other controls
    /// like a MultiColumnTreeList or a ListBox to visualize the hierarchy as a &quot;flat list&quot;. </para>
    /// <para> Set <see
    /// cref="P:RadiantQ.Windows.Controls.FlatteningTreeView.AutoExpandNodes">AutoExpandNodes
    /// </see>to true to load the tree fully-expanded. Or use the <see
    /// cref="M:RadiantQ.Windows.Controls.FlatteningTreeView.ExpandRealizedNodes">ExpandRealizedNodes
    /// </see>(with AutoExpandNodes set to true) at any time to get expand-all
    /// functionality. </para>
    /// </remarks>
    public class FlatteningTreeView : TreeView
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public FlatteningTreeView()
        {
            this.DefaultStyleKey = typeof(FlatteningTreeView);

            this._flatTreeViewItemsList = new ObservableCollection<FlatteningTreeViewItem>();
            this.FlatItemsSource = new FlatItemsSourceList();


            this.AutoExpandNodes = true;
        }

        ObservableCollection<FlatteningTreeViewItem> _flatTreeViewItemsList;

        internal ObservableCollection<FlatteningTreeViewItem> FlatTreeViewItemsList
        {
            get { return this._flatTreeViewItemsList; }
        }

        /// <summary>
        /// The content for the title header.
        /// </summary>
        public object TitleContent
        {
            get { return (object)GetValue(TitleContentProperty); }
            set { SetValue(TitleContentProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.FlatteningTreeView.TitleContent">TitleContent</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty TitleContentProperty =
            DependencyProperty.Register("TitleContent", typeof(object), typeof(FlatteningTreeView), new PropertyMetadata("Title"));

        /// <summary>
        /// The height for the header. Default is 20.
        /// </summary>
        public double HeaderHeight
        {
            get { return (double)GetValue(HeaderHeightProperty); }
            set { SetValue(HeaderHeightProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.FlatteningTreeView.HeaderHeight">HeaderHeight</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty HeaderHeightProperty =
            DependencyProperty.Register("HeaderHeight", typeof(double), typeof(FlatteningTreeView), new PropertyMetadata(20.0, HeaderHeightChanged));

        /// <summary>
        /// A list of <see
        /// cref="T:RadiantQ.Windows.Controls.FlatNodeData">FlatNodeData</see>
        /// instances that represents a flat view of the bound hierarchical data.
        /// </summary>
        public FlatItemsSourceList FlatItemsSource
        {
            get { return (FlatItemsSourceList)GetValue(FlatItemsSourceProperty); }
            set { SetValue(FlatItemsSourceProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.FlatteningTreeView.FlatItemsSource">FlatItemsSource</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty FlatItemsSourceProperty =
            DependencyProperty.Register("FlatItemsSource", typeof(FlatItemsSourceList), typeof(FlatteningTreeView), new PropertyMetadata(null));


        /// <summary>
        /// Specifies the row height for each row in the tree.
        /// </summary>
        public double RowHeight
        {
            get { return (double)GetValue(RowHeightProperty); }
            set { SetValue(RowHeightProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.FlatteningTreeView.RowHeight">RowHeight</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty RowHeightProperty =
            DependencyProperty.Register("RowHeight", typeof(double), typeof(FlatteningTreeView), new PropertyMetadata(20.0));


        private static void HeaderHeightChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            FlatteningTreeView tree = source as FlatteningTreeView;
        }
        /// <summary>
        /// override.
        /// </summary>
        /// <returns>Returns an instance of FlatteningTreeViewItem.</returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new FlatteningTreeViewItem(this, null, 0);
        }
        /// <summary>
        /// override.
        /// </summary>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            FlatteningTreeViewItem tvi = element as FlatteningTreeViewItem;
            int index = this.ItemContainerGenerator.IndexFromContainer(element);
            if (index == 0)
            {
                this.FlatTreeViewItemsList.Insert(0, tvi);
                this.FlatItemsSource.Insert(0, new FlatNodeData(tvi));
            }
            else
            {
                FlatteningTreeViewItem nextItem = this.GetNextItem(tvi);
                if (nextItem != null)
                {
                    int nextItemIndex = this.FlatTreeViewItemsList.IndexOf(nextItem);
                    this.FlatTreeViewItemsList.Insert(nextItemIndex, tvi);
                    this.FlatItemsSource.Insert(nextItemIndex, new FlatNodeData(tvi));
                }
                else
                {
                    this.FlatTreeViewItemsList.Add(tvi);
                    this.FlatItemsSource.Add(new FlatNodeData(tvi));
                }
            }
            base.PrepareContainerForItemOverride(element, item);
        }
        /// <summary>
        /// override.
        /// </summary>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            FlatteningTreeViewItem tvi = element as FlatteningTreeViewItem;
            this.ClearHierarchyFromList(tvi);
            base.ClearContainerForItemOverride(element, item);
        }
        internal void ClearHierarchyFromList(FlatteningTreeViewItem tvi)
        {
            this.FlatTreeViewItemsList.Remove(tvi);
            this.FlatItemsSource.RemoveSource(tvi.DataContext);
            for (int i = 0; i < tvi.Items.Count; i++)
            {
                FlatteningTreeViewItem child = tvi.ItemContainerGenerator.ContainerFromIndex(i) as FlatteningTreeViewItem;
                // Could be null if the child was not shown yet.
                if(child != null)
                    this.ClearHierarchyFromList(child);
            }
        }
        /// <summary>
        /// Returns the next item in the top-level.
        /// </summary>
        /// <param name="child">An item at the top level.</param>
        /// <returns>The next item at the top level.</returns>
        public FlatteningTreeViewItem GetNextItem(FlatteningTreeViewItem child)
        {
            int index = this.ItemContainerGenerator.IndexFromContainer(child);
            return this.ItemContainerGenerator.ContainerFromIndex(index + 1) as FlatteningTreeViewItem;
        }
        internal void NotifyChildrenOfExpansionStateChange(FlatteningTreeViewItem tvi)
        {
            this.NotifyChildrenOfExpansionStateChangeRecursive(tvi);
            this.OnNodeExpansionStateChanged(new NodeExpansionStateChangedEventArgs(tvi));
        }

        private void NotifyChildrenOfExpansionStateChangeRecursive(FlatteningTreeViewItem tvi)
        {
            for (int i = 0; i < tvi.Items.Count; i++)
            {
                FlatteningTreeViewItem child = tvi.ItemContainerGenerator.ContainerFromIndex(i) as FlatteningTreeViewItem;
                // Could be null if the child was not shown yet.
                if (child != null)
                {
                    child.OnIsVisibleChanged();
                    if (child.IsExpanded)
                        this.NotifyChildrenOfExpansionStateChangeRecursive(child);
                }
            }
        }

        /// <summary>
        /// Call this to fire the NodeVisibilityChanged event.
        /// </summary>
        protected virtual void OnNodeExpansionStateChanged(NodeExpansionStateChangedEventArgs args)
        {
            if (this.NodeExpansionStateChanged != null)
                this.NodeExpansionStateChanged(this, args);
        }
        /// <summary>
        /// Fired when a node's visibility changed.
        /// </summary>
        public event NodeExpansionStateChangedEventHandler NodeExpansionStateChanged;

        /// <summary>
        /// Collapses all nodes.
        /// </summary>
        public void CollapseAll()
        {
            foreach (FlatteningTreeViewItem tvi in this.FlatTreeViewItemsList)
            {
                tvi.IsExpanded = false;
            }
        }
        /// <summary>
        /// Specifies if realized nodes are automatically expanded. Default is true. Call this before setting the
        /// ItemsSource on this tree to load the tree fully-expanded or collapsed.
        /// </summary>
        public bool AutoExpandNodes { get; set; }

        /// <summary>
        /// Expands all realized nodes. For an "Expand All" functionality, set AutoExpandNodes to true before calling this method.
        /// </summary>
        public void ExpandRealizedNodes()
        {
            foreach (FlatteningTreeViewItem tvi in this.FlatTreeViewItemsList)
            {
                tvi.IsExpanded = true;
            }
        }
        private const double EXPANDER_WIDTH = 12;
        public static double LevelToIndentWidth(int level, bool isparent)
        {
            double width = 0d;

            if (level > 0)
                width += level * EXPANDER_WIDTH;

            if (isparent == false)
                width += EXPANDER_WIDTH;

            return width;
        }
    }
    /// <summary>
    /// The type that represents the nodes in a <see
    /// cref="T:RadiantQ.Windows.Controls.FlatteningTreeView">FlatteningTreeView</see>.
    /// </summary>
    public class FlatteningTreeViewItem : TreeViewItem
    {
        FlatteningTreeView _parent;
        FlatteningTreeViewItem _parentItem;
        /// <summary>
        /// Constructor.
        /// </summary>
        public FlatteningTreeViewItem(FlatteningTreeView parent, FlatteningTreeViewItem parentItem, int level)
        {
            this.Level = level;
            this._parent = parent;
            this._parentItem = parentItem;
        }
        Grid TopGrid { get; set; }
        FrameworkElement HeaderElement { get; set; }

        /// <summary>
        /// Returns the parent FlatteningTreeViewItem instance.
        /// </summary>
        public FlatteningTreeViewItem ParentItem { get { return this._parentItem; } }

        /// <summary>
        /// The level of this node in the tree.
        /// </summary>
        public int Level { get; set; }

        /// <summary>
        /// override.
        /// </summary>
        public override void OnApplyTemplate()
        {
            this.TopGrid = Extensions.GetChild<Grid>(this, true);
            if (this.TopGrid == null)
                System.Diagnostics.Debug.WriteLine("Did not find a Grid within the TreeViewItem template, this could result in incorrect height setting for each tree node.");
            else
                this.OnHeaderHeightChanged();

            // Center it since the height could be longer than auto.
            this.HeaderElement = this.GetTemplateChild("Header") as FrameworkElement;
            if (this.HeaderElement != null)
            {
                Rectangle selection = this.GetTemplateChild("Selection") as Rectangle;
                if (selection != null)
                    selection.SizeChanged += new SizeChangedEventHandler(selection_SizeChanged);
            }

            // Delay setting this up until ApplyTemplate because only then it's margin values will be known
            // at the converter.
            Binding binding = new Binding("RowHeight");
            binding.Source = _parent;
            binding.Converter = new RowHeightToItemHeightConverter();
            binding.ConverterParameter = this;
            this.SetBinding(HeaderHeightProperty, binding);

            base.OnApplyTemplate();

            // Currently expanding all nodes by default to allow the MultiColumnTreeList to know the full hierarchy at load time.
            if(this._parent.AutoExpandNodes)
                this.IsExpanded = true;
        }


        // Some alignment changes on the default template to make the text appear centered in the nodes.
        void selection_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Rectangle selRect = sender as Rectangle;
            Rectangle hover = Extensions.GetChild<Rectangle>(this.HeaderElement, false);
            if (hover != null)
            {
                hover.Height = selRect.ActualHeight;
            }
            ContentPresenter cp = Extensions.GetChild<ContentPresenter>(this.HeaderElement, false);
            if (cp != null)
                cp.VerticalAlignment = VerticalAlignment.Center;
        }
        /// <summary>
        /// Specifies the height for this node.
        /// </summary>
        public double HeaderHeight
        {
            get { return (double)GetValue(HeaderHeightProperty); }
            private set { SetValue(HeaderHeightProperty, value); }
        }

        /// <summary>
        /// Identifies the <see cref="P:RadiantQ.Windows.Controls.FlatteningTreeViewItem.HeaderHeight">HeaderHeight</see>
        /// dependency property.
        /// </summary>
        public static readonly DependencyProperty HeaderHeightProperty =
            DependencyProperty.Register("HeaderHeight", typeof(double), typeof(FlatteningTreeViewItem), new PropertyMetadata(0.0, HeaderHeightChanged));


        private static void HeaderHeightChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs args)
        {
            FlatteningTreeViewItem tvi = depObject as FlatteningTreeViewItem;
            tvi.OnHeaderHeightChanged();
        }
        private void OnHeaderHeightChanged()
        {
            if(this.TopGrid != null)
                this.TopGrid.RowDefinitions[0].Height = new GridLength(this.HeaderHeight);
        }
        /// <summary>
        /// Specifies if this node is currently visible.
        /// </summary>
        public bool IsVisible
        {
            get
            {
                if (this._parentItem == null)
                    return true;
                else
                    return this._parentItem.IsExpanded && this._parentItem.IsVisible;
            }
        }
        /// <summary>
        /// Fired when this node's visibility changes.
        /// </summary>
        public event EventHandler IsVisibleChanged;
        /// <summary>
        /// Called to fire the IsVisibleChanged event.
        /// </summary>
        protected internal virtual void OnIsVisibleChanged()
        {
            if (this.IsVisibleChanged != null)
                this.IsVisibleChanged(this, EventArgs.Empty);
        }
        
        ObservableCollection<FlatteningTreeViewItem> FlatTreeViewItemsList
        {
            get { return this._parent.FlatTreeViewItemsList; }
        }
        FlatItemsSourceList FlatItemsSource
        {
            get { return this._parent.FlatItemsSource; }
        }
        /// <summary>
        /// override.
        /// </summary>
        /// <returns>Returns an instance of FlatteningTreeViewItem.</returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new FlatteningTreeViewItem(this._parent, this, this.Level + 1);
        }
        /// <summary>
        /// Override
        /// </summary>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            FlatteningTreeViewItem tvi = element as FlatteningTreeViewItem;
            int index = this.ItemContainerGenerator.IndexFromContainer(element);
            if (index == 0)
            {
                int selfIndex = this.FlatTreeViewItemsList.IndexOf(this);
                this.FlatTreeViewItemsList.Insert(selfIndex + 1, tvi);
                this.FlatItemsSource.Insert(selfIndex + 1, new FlatNodeData(tvi));
            }
            else
            {
                FlatteningTreeViewItem nextItem = this.GetNextItem(tvi);
                if (nextItem != null)
                {
                    int nextItemIndex = this.FlatTreeViewItemsList.IndexOf(nextItem);
                    this.FlatTreeViewItemsList.Insert(nextItemIndex, tvi);
                    this.FlatItemsSource.Insert(nextItemIndex, new FlatNodeData(tvi));
                }
                else
                {
                    this.FlatTreeViewItemsList.Add(tvi);
                    this.FlatItemsSource.Add(new FlatNodeData(tvi));
                }
            }
            base.PrepareContainerForItemOverride(element, item);
        }
        /// <summary>
        /// Override
        /// </summary>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            FlatteningTreeViewItem tvi = element as FlatteningTreeViewItem;
            this._parent.ClearHierarchyFromList(tvi);
            base.ClearContainerForItemOverride(element, item);
        }
        /// <summary>
        /// Returns the next sibling child item.
        /// </summary>
        /// <param name="child">A child item.</param>
        /// <returns>The next sibling item.</returns>
        public FlatteningTreeViewItem GetNextItem(FlatteningTreeViewItem child)
        {
            int index = this.ItemContainerGenerator.IndexFromContainer(child);
            FlatteningTreeViewItem nextItem = this.ItemContainerGenerator.ContainerFromIndex(index + 1) as FlatteningTreeViewItem;
            if (nextItem != null)
                return nextItem;
            else
            {
                if (this._parentItem != null)
                    return this._parentItem.GetNextItem(this);
                else
                    return this._parent.GetNextItem(this);
            }
        }
        /// <summary>
        /// override.
        /// </summary>
        protected override void OnExpanded(RoutedEventArgs e)
        {
            base.OnExpanded(e);
            this._parent.NotifyChildrenOfExpansionStateChange(this);
        }
        /// <summary>
        /// override.
        /// </summary>
        protected override void OnCollapsed(RoutedEventArgs e)
        {
            base.OnCollapsed(e);
            this._parent.NotifyChildrenOfExpansionStateChange(this);
        }

        class RowHeightToItemHeightConverter : IValueConverter
        {
            #region IValueConverter Members

            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                double rowHeight = (double)value;
                Thickness margin = ((FlatteningTreeViewItem)parameter).Margin;
                return rowHeight - margin.Top - margin.Bottom;
            }

            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }

            #endregion
        }

    }
    /// <summary>
    /// A list of <see
    /// cref="T:RadiantQ.Windows.Controls.IFlatNodeData">IFlatNodeData</see>
    /// instances that represents a hierarchical data source as a flat list.
    /// </summary>
    public class FlatItemsSourceList : ObservableCollection<IFlatNodeData>
    {
        /// <summary>
        /// Removes the specified data object from the list.
        /// </summary>
        /// <param name="dataSource"></param>
        public void RemoveSource(object dataSource)
        {
            int i = -1;
            foreach (IFlatNodeData nodeData in this)
            {
                i++;
                if (nodeData.Data == dataSource)
                {
                    this.RemoveAt(i);
                    break;
                }
            }
        }
        /// <summary>
        /// Given a data source object, returns the <see
        /// cref="T:RadiantQ.Windows.Controls.IFlatNodeData">IFlatNodeData
        /// </see>instance corresponding to that data source.
        /// </summary>
        /// <param name="dataSource">The data source object to find.</param>
        /// <returns>
        /// The corresponding IFlatNodeData instance.
        /// </returns>
        public IFlatNodeData GetItemFromDataSource(object dataSource)
        {
            foreach (IFlatNodeData nodeData in this)
            {
                if (nodeData.Data == dataSource)
                {
                    return nodeData;
                }
            }
            return null;
        }
    }
    /// <summary>
    /// Represents a node's data in a <see
    /// cref="T:RadiantQ.Windows.Controls.FlatteningTreeView">FlatteningTreeView</see>.
    /// </summary>
    public interface IFlatNodeData
    {
        /// <summary>
        /// Specifies if this node is a parent type. It's possible there are no children for this parent node.
        /// </summary>
        bool IsParentType { get; }

        /// <summary>
        /// Specifies if there are any children for this parent.
        /// </summary>
        bool IsExpandable { get; }

        /// <summary>
        /// Specifies if this node is currently visible.
        /// </summary>
        bool IsVisibleInTree { get; }
        /// <summary>
        /// A reference to the underlying bound object.
        /// </summary>
        object Data { get; }
        /// <summary>
        /// The level of this node.
        /// </summary>
        int Level { get; }
    }
    /// <summary>
    /// An instance of IFlatNodeData representing a node's data in a <see
    /// cref="T:RadiantQ.Windows.Controls.FlatteningTreeView">FlatteningTreeView</see>.
    /// </summary>
    public class FlatNodeData : INotifyPropertyChanged, IFlatNodeData
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public FlatNodeData(FlatteningTreeViewItem item)
        {
            this._data = item.DataContext;
            item.IsVisibleChanged += new EventHandler(item_IsVisibleChanged);
            this._isvisible = item.IsVisible;
            this.TreeViewItem = item;

            this.TreeViewItem.ItemContainerGenerator.ItemsChanged += new ItemsChangedEventHandler(ItemContainerGenerator_ItemsChanged);

            if(this._data is INotifyPropertyChanged)
                ((INotifyPropertyChanged)this._data).PropertyChanged += new PropertyChangedEventHandler(FlatNodeData_PropertyChanged);
        }

        void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e)
        {
            this.OnPropertyChanged("IsParentType");
            this.OnPropertyChanged("IsExpandable");
        }

        void FlatNodeData_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            this.OnPropertyChanged("Data");
        }

        void item_IsVisibleChanged(object sender, EventArgs e)
        {
            this.IsVisibleInTree = ((FlatteningTreeViewItem)sender).IsVisible;
        }
        /// <summary>
        /// Specifies if this node is a parent type. It's possible there are no children for this parent node.
        /// </summary>
        public bool IsParentType { get { return this.TreeViewItem.ItemsSource != null; } }

        /// <summary>
        /// Specifies if there are any children for this parent.
        /// </summary>
        public bool IsExpandable { get { return this.TreeViewItem.Items != null && this.TreeViewItem.Items.Count > 0; } }

        /// <summary>
        /// Provides a reference to the underlying FlatteningTreeViewItem instance.
        /// </summary>
        public FlatteningTreeViewItem TreeViewItem { get; private set; }

        private bool _isvisible;
        /// <summary>
        /// Specifies if this node is currently visible.
        /// </summary>
        public bool IsVisibleInTree 
        {
            get { return _isvisible; } 
            private set 
            {
                if (this._isvisible != value)
                {
                    this._isvisible = value;
                    this.OnPropertyChanged("IsVisibleInTree");
                }
            }
        }
        private object _data;
        /// <summary>
        /// A reference to the underlying bound object. If the object implements INotifyPropertyChanged interface,
        /// then changes there are propogated through this PropertyChanged event called for this "Data" property.
        /// This allows you to bind to this Data in a Binding instance.
        /// </summary>
        public object Data 
        {
            get
            {
                return this._data;
            }
            set
            {
                // Intentionally set blank
            }
        }
        /// <summary>
        /// The level of this node.
        /// </summary>
        public int Level { get { return TreeViewItem.Level;} }


        //public FlatNodeData Self { get { return this; } }

        #region INotifyPropertyChanged Members
        /// <summary>
        /// Fired to notify that a property has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        /// <summary>
        /// Called to raise the PropertyChanged event.
        /// </summary>
        protected virtual void OnPropertyChanged(string name)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #endregion

        
    }
    /// <summary>
    /// A converter that specifies the indent width to assign for a level in a MultiColumnTreeList while representing the tree level.
    /// </summary>
    internal class LevelToIndentWidthConverter : IValueConverter
    {
        #region IValueConverter Members

        /// <summary>
        /// Converts a FlatNodeData instance to a indent width in double.
        /// </summary>
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            FlatNodeData nodeData = value as FlatNodeData;
            return FlatteningTreeView.LevelToIndentWidth(nodeData.Level, nodeData.IsParentType);
        }
        /// <summary>
        /// Not implemented.
        /// </summary>
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
    public delegate void NodeExpansionStateChangedEventHandler(object sender, NodeExpansionStateChangedEventArgs e);

    public class NodeExpansionStateChangedEventArgs : EventArgs
    {
        public FlatteningTreeViewItem TreeViewItem { get; private set; }

        public NodeExpansionStateChangedEventArgs(FlatteningTreeViewItem tvi)
        {
            this.TreeViewItem = tvi;
        }
    }
    /// <summary>
    /// Type containing some utitlity methods.
    /// </summary>
    public class Extensions
    {
        /// <summary>
        /// Get a child of the specified UIElement type.
        /// </summary>
        /// <typeparam name="T">The UIElement type of child to find.</typeparam>
        /// <param name="parent">The parent from where to start the search.</param>
        /// <param name="onlyVisible">Find only visible instances.</param>
        /// <returns>The found child.</returns>
        public static T GetChild<T>(DependencyObject parent, bool onlyVisible) where T : UIElement
        {
            List<T> matches = new List<T>();
            GetChildren<T>(parent, ref matches, false, onlyVisible);
            if (matches.Count > 0)
                return matches[0];
            else
                return null;
        }

        /// <summary>
        /// Gets children of a specific Type.
        /// </summary>
        /// <typeparam name="T">The type of children.</typeparam>
        /// <param name="parent">The parent whose children we want to find.</param>
        /// <param name="children">The list which will contain the matched children.</param>
        /// <param name="recurseThroughMatches">Specifies if the we should continue searching inside a matched element.</param>
        /// <param name="onlyVisible">true to only find visible elements.</param>
        public static void GetChildren<T>(DependencyObject parent, ref List<T> children, bool recurseThroughMatches, bool onlyVisible)
            where T : UIElement
        {
            int count = VisualTreeHelper.GetChildrenCount(parent);
            if (count > 0)
            {
                for (int i = 0; i < count; i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                    if (child is T)
                    {
                        T uiElement = child as T;
                        if (onlyVisible == false || uiElement.Visibility == Visibility.Visible)
                        {
                            children.Add(uiElement);
                            if (recurseThroughMatches == false)
                                continue;
                        }
                    }
                    GetChildren<T>(child, ref children, recurseThroughMatches, onlyVisible);
                }
            }
        }
    }
}
